package controller.sequentParser;

import java.io.IOException;
import java.io.Reader;
import java.util.regex.Pattern;

import model.ParseError;
import model.sequent.SequentToken;

/**
 * Contains methods for converting input data to tokens, which makes it easier to parse the input data
 * 
 * @author Sjaak Smetsers (number?)
 * @author Karen Berntsen (s0815799)
 * @author Erik Stens (s0615374)
 */
public class SequentTokenizer {
	private static final int		EOF					= -1;
	private static final Pattern	atomStartPattern	= Pattern.compile("[a-zA-Z]");
	private static final Pattern	atomMidPattern		= Pattern.compile("[a-zA-Z0-9_]");
	
	private Reader					input;
	private int						currentChar;
	
	private SequentToken			currentToken;
	
	private StringBuilder			atomToken;
	
	public SequentTokenizer(Reader input) throws ParseError {
		this.input = input;
		try {
			currentChar = input.read();
			skipLayout();
		} catch (IOException ex) {
			throw new ParseError("IOException: " + ex);
		}
		nextToken();
	}
	
	public void nextToken() throws ParseError {
		try {
			scan();
			skipLayout();
		} catch (IOException ex) {
			throw new ParseError("IOException: " + ex);
		}
	}
	
	public SequentToken currentToken() {
		return currentToken;
	}
	
	public String getAtomName() throws ParseError {
		if (currentToken == SequentToken.ATOM) {
			return atomToken.toString();
		} else {
			throw new ParseError("No atomName available for: " + currentToken);
		}
	}
	
	/**
	 * Skips white spaces
	 * 
	 * @throws IOException
	 */
	private void skipLayout() throws IOException {
		while (currentChar != EOF && Character.isWhitespace(currentChar)) {
			currentChar = input.read();
		}
	}
	
	/**
	 * Scans the input for known tokens. Throws an exception if the input can't be read or if the input can't be converted to a known token.
	 * 
	 * @throws IOException
	 * @throws ParseError
	 */
	private void scan() throws IOException, ParseError {
		if (currentChar == EOF) {
			currentToken = SequentToken.EOF;
		} else {
			switch (currentChar) {
				case '@':
					currentChar = input.read();
					currentToken = SequentToken.SEQUENT;
					break;
				case '=':
					currentChar = input.read();
					currentToken = SequentToken.BI_IMPLICATE;
					break;
				case '>':
					currentChar = input.read();
					currentToken = SequentToken.IMPLICATE;
					break;
				case '|':
					currentChar = input.read();
					currentToken = SequentToken.OR;
					break;
				case '&':
					currentChar = input.read();
					currentToken = SequentToken.AND;
					break;
				case '!':
					currentChar = input.read();
					currentToken = SequentToken.NOT;
					break;
				case '(':
					currentChar = input.read();
					currentToken = SequentToken.BRACKET_OPEN;
					break;
				case ')':
					currentChar = input.read();
					currentToken = SequentToken.BRACKET_CLOSE;
					break;
				case ',':
					currentChar = input.read();
					currentToken = SequentToken.COMMA;
					break;
				default:
					if (isAtomChar(currentChar, atomStartPattern)) {
						scanAtom();
					} else {
						throw new ParseError("Illegal input character");
					}
			}
		}
	}
	
	private boolean isAtomChar(int character, Pattern atomPattern) {
		return atomPattern.matcher(Character.toString((char) currentChar)).matches();
	}
	
	/**
	 * After scan() found the first character of an atom token, keeps scanning the input for more characters that matches the atomMidPattern pattern.
	 * 
	 * @throws IOException
	 */
	private void scanAtom() throws IOException {
		atomToken = new StringBuilder(Character.toString((char) currentChar));
		currentToken = SequentToken.ATOM;
		
		for (currentChar = input.read(); isAtomChar(currentChar, atomMidPattern); currentChar = input.read()) {
			atomToken.append((char) currentChar);
		}
	}
}
